package org.jruby.util;


/**
 * A Simple cache which maintains a collection of classes that can potentially be shared among
 * multiple runtimes (or whole JVM).
 */
public class ClassCache {
    /**
     * The ClassLoader this class cache will use for any classes generated through it.  It is 
     * assumed that the classloader provided will be a parent loader of any runtime using it.
     * @param classLoader to use to generate shared classes
     */
    public  function ClassCache(classLoader:ClassLoader, max:int){
        thisLoader = classLoader;
        this.max = max;
    }
    
    public  function ClassCache(classLoader:ClassLoader){
        this(classLoader, -1);
    }
    
    public interface ClassGenerator {
        byte[] bytecode();
        String name();
    }
    
    private static class KeyedClassReference extends WeakReference<Class> {
        private  var key:Object;
        
        public KeyedClassReference(Object key, Class referent, ReferenceQueue<Class> referenceQueue) {
            super(referent, referenceQueue);
            
            this.key = key;
        }
        
        public  function getKey():Object{
            return key;
        }
    }
    
    private static class OneShotClassLoader extends URLClassLoader {
        private static var DEFAULT_DOMAIN: ProtectionDomain =  
            JRubyClassLoader.getProtectionDomain();
        
        public  function OneShotClassLoader(parent:ClassLoader){
            super(new URL[0], parent);
        }
        
        // Change visibility so others can see it
        public  function addURL(url:URL):void{
            super.addURL(url);
        }

        public Class<?> defineClass(String name, byte[] bytes) {
            return super.defineClass(name, bytes, 0, bytes.length, DEFAULT_DOMAIN);
         }

        public Class<?> defineClass(String name, byte[] bytes, ProtectionDomain domain) {
           return super.defineClass(name, bytes, 0, bytes.length, domain);
        }
    }
    
    private  var referenceQueue: ReferenceQueue =  new ReferenceQueue();
    private  var cache: Dictionary =  
        new ConcurrentHashMap();
    private  var classLoader:ClassLoader;
    private  var max:int;
    
    public  function getClassLoader():ClassLoader{
        return classLoader;
    }

    public  function getMax():int{
        return max;
    }
    
    public Class cacheClassByKey(Object key, ClassGenerator classGenerator) 
        throws ClassNotFoundException {
        WeakReference<Class> weakRef = cache.get(key);
        var contents: Class =  null;
        if (weakRef != null) contents = weakRef.get();
        
        if (weakRef == null || contents == null) {
            if (isFull()) return null;
            
            var oneShotCL: OneShotClassLoader =  new OneShotClassLoader(getClassLoader());
            contents = (Class)oneShotCL.defineClass(classGenerator.name(), classGenerator.bytecode());
            
            cache.put(key, new KeyedClassReference(key, contents, referenceQueue));
        }
        
        return contents;
    }
    
    public  function isFull():Boolean{
        cleanup();
        return max > 0 && cache.size() >= max;
    }
    
    private  function cleanup():void{
        var reference:KeyedClassReference;
        while ((reference = (KeyedClassReference)referenceQueue.poll()) != null) {
            cache.remove(reference.getKey());
        }
    }
}
}